package jmine.tec.hydra.domain.message;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Blob;
import java.sql.SQLException;
import java.util.Map;

import javax.persistence.Basic;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToOne;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import javax.persistence.Transient;

import jmine.tec.hydra.api.HydraMessage;
import jmine.tec.hydra.api.HydraRequest;
import jmine.tec.hydra.api.MessageDirection;
import jmine.tec.hydra.domain.HydraRequestImpl;
import jmine.tec.hydra.domain.HydraSystem;
import jmine.tec.persist.annotation.Alias;
import jmine.tec.persist.annotation.Comment;
import jmine.tec.persist.annotation.Comments;
import jmine.tec.persist.annotation.Constraint;
import jmine.tec.persist.annotation.Index;
import jmine.tec.utils.io.IOUtils;

import org.hibernate.Hibernate;
import org.hibernate.annotations.Cascade;
import org.hibernate.annotations.CascadeType;
import org.hibernate.annotations.CollectionOfElements;
import org.hibernate.annotations.MapKey;
import org.hibernate.annotations.Type;

import bancosys.tec.component.Documentation;
import bancosys.tec.persist.bussobj.PersistableBusinessObject;
import bancosys.tec.persist.dao.BaseDAO;
import bancosys.tec.persist.dao.BeanNotFoundException;
import bancosys.tec.persist.exception.PersistenceException;
import bancosys.tec.utils.date.Timestamp;

/**
 * Representa uma mensagem de comunicação entre sistemas integrados.
 * 
 * @author piercio
 */
@Entity
@Table(name = "HYDRA_MESSAGE")
@Alias("HYDMSG")
@SequenceGenerator(name = "SEQ_HYDMSG", sequenceName = "SEQ_HYDMSG")
@Documentation("ARMAZENA AS MENSAGENS TROCADAS ENTRE OS SISTEMAS INTEGRADOS.")
public class HydraMessageImpl extends PersistableBusinessObject implements HydraMessage {

    private Long id;

    private HydraRequest request;

    private MessageDirection direction;

    private HydraConversation conversation;

    private Map<String, String> properties;

    private Timestamp timestamp;

    private Blob body;

    private Long requestId;

    /**
     * {@inheritDoc}
     */
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_HYDMSG")
    @Column(name = "COD_HYDRA_MESSAGE", nullable = false)
    @Documentation("CODIGO DA MENSAGEM.")
    public Long getId() {
        return this.id;
    }

    /**
     * @param id o valor do id da mensagem.
     */
    public void setId(Long id) {
        this.id = id;
    }

    /**
     * @return the request
     */
    // XXX (mmr) : relacionamento sem FK para permitir expurgo de HydraRequest
    @Transient
    public HydraRequest getRequest() {
        if (this.request == null) {
            BaseDAO<HydraRequestImpl> dao = getController().getDAOFactory().getGenericDAO(HydraRequestImpl.class);
            try {
                this.request = dao.findByPk(this.requestId);
            } catch (BeanNotFoundException e) {
                // do nothing
            }
        }
        return this.request;
    }

    /**
     * @param request the request to set
     */
    public void setRequest(HydraRequest request) {
        this.request = request;
        this.requestId = (Long) request.getPk();
    }

    /**
     * @return id de request.
     */
    @SuppressWarnings("unused")
    @Index(suffix = "JT_1")
    @Column(name = "COD_HYDRA_REQUEST")
    @Documentation("CODIGO DA REQUISICAO QUE TRATOU ESTA MENSAGEM")
    private Long getRequestId() {
        return this.requestId;
    }

    /**
     * Define valor para campo requestId.
     * 
     * @param requestId valor para campo requestId.
     */
    @SuppressWarnings("unused")
    private void setRequestId(Long requestId) {
        this.requestId = requestId;
    }

    /**
     * @return the direction
     */
    @Constraint(name = "CK_HYDMSG_0")
    @Column(name = "MESSAGE_DIRECTION", nullable = false)
    @Documentation("DIRECAO DA MENSAGEM. 0 - RECEBIDA; 1 - ENVIADA")
    public MessageDirection getDirection() {
        return this.direction;
    }

    /**
     * @param direction the direction to set
     */
    public void setDirection(MessageDirection direction) {
        this.direction = direction;
    }

    /**
     * {@inheritDoc}
     */
    @Transient
    public HydraSystem getSystem() {
        if (this.conversation == null) {
            return null;
        }
        return this.conversation.getSystem();
    }

    /**
     * {@inheritDoc}
     */
    @Column(name = "TS_MESSAGE", nullable = false)
    @Type(type = "bancosys.tec.persist.hibernate.type.TimestampType")
    @Documentation("DATA/HORA DA MENSAGEM.")
    public Timestamp getTimestamp() {
        return this.timestamp;
    }

    /**
     * Define a data/hora do envio/recebimento da mensagem.
     * 
     * @param timestamp a data/hora do envio/recebimento da mensagem.
     */
    public void setTimestamp(Timestamp timestamp) {
        this.timestamp = timestamp;
    }

    /**
     * @return conteudo da mensagem.
     */
    @Basic(fetch = FetchType.LAZY)
    @Type(type = "bancosys.tec.persist.hibernate.type.UpdatableBlobType")
    @Column(name = "MESSAGE")
    @Documentation("CONTEUDO DA MENSAGEM")
    public Blob getBodyBlob() {
        return this.body;
    }

    /**
     * Define conteudo da mensagem.
     * 
     * @param bodyBlob corpo da mensagem.
     */
    public void setBodyBlob(Blob bodyBlob) {
        this.body = bodyBlob;
    }

    /**
     * {@inheritDoc}
     */
    @Transient
    public InputStream getBody() {
        if (this.getBodyBlob() == null) {
            return null;
        }
        try {
            return this.getBodyBlob().getBinaryStream();
        } catch (SQLException e) {
            throw new PersistenceException(e);
        }
    }

    /**
     * Define o conteúdo da mensagem enviada/recebida.
     * 
     * @param bodyInput o conteúdo da mensagem enviada/recebida.
     */
    public void setBody(InputStream bodyInput) {
        try {
            this.setBodyBlob(Hibernate.createBlob(bodyInput));
        } catch (IOException e) {
            throw new PersistenceException(e);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Transient
    public String getBodyAsString() throws IOException {
        if (this.conversation != null && this.conversation.getSystem() != null) {
            return this.getBodyAsString(this.conversation.getSystem().getEncoding());
        }
        if (this.request != null && this.request.getSystem() != null) {
            return this.getBodyAsString(this.request.getSystem().getEncoding());
        }
        return this.getBodyAsString("UTF-8");
    }

    /**
     * {@inheritDoc}
     */
    @Transient
    public String getBodyAsString(String charsetName) throws IOException {
        InputStream input = this.getBody();
        if (input == null) {
            return null;
        } else {
            ByteArrayOutputStream ret = new ByteArrayOutputStream();
            IOUtils.copyFully(input, ret);
            return new String(ret.toByteArray(), charsetName);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Index(suffix = "JT_0")
    @ManyToOne
    @Cascade(CascadeType.LOCK)
    @JoinColumn(name = "COD_HYDRA_CONVERSATION")
    @Documentation("CONVERSA QUE CONTEM ESSA MENSAGEM.")
    public HydraConversation getConversation() {
        return this.conversation;
    }

    /**
     * @param conversation o valor para o campo conversation
     */
    public void setConversation(HydraConversation conversation) {
        this.conversation = conversation;
    }

    /**
     * @return the properties
     */
    @Index(suffix = "JT_0", columns = "COD_HYDRA_MESSAGE")
    @CollectionOfElements
    @Cascade({ CascadeType.ALL, CascadeType.DELETE_ORPHAN })
    @JoinTable(name = "HYDRA_MSG_PROP", joinColumns = @JoinColumn(name = "COD_HYDRA_MESSAGE"))
    @Alias("HYMSPR")
    @MapKey(columns = { @Column(name = "PROP_NAME", nullable = false) })
    @Column(name = "PROP_VALUE", nullable = false)
    @Comments({ @Comment(table = "HYDRA_MSG_PROP", value = "VALORES MAPEADOS DAS PROPRIEDADES DO DRIVER"),
            @Comment(table = "HYDRA_MSG_PROP", column = "COD_HYDRA_MESSAGE", value = "CODIGO DA MENSAGEM QUE CONTEM AS PROPRIEDADES"),
            @Comment(table = "HYDRA_MSG_PROP", column = "PROP_NAME", value = "NOME DA PROPRIEDADE DO DRIVER"),
            @Comment(table = "HYDRA_MSG_PROP", column = "PROP_VALUE", value = "VALOR DA PROPRIEDADE DO DRIVER") })
    public Map<String, String> getProperties() {
        return this.properties;
    }

    /**
     * @param properties the properties to set
     */
    public void setProperties(Map<String, String> properties) {
        this.properties = properties;
    }

    /**
     * {@inheritDoc}
     */
    @Transient
    public String getAction() {
        return this.conversation == null ? null : this.conversation.getSpecification().getAction();
    }
}
